

var VecMultK = function(v,k){
    return cc.p(v.x * k, v.y * k);
};
var VecDivK = function(v,k){
    return cc.p(v.x / k, v.y / k);
};
var VecMultVec = function(v,v2){
    return cc.p(v.x * v2.x, v.y * v2.y);
};
var VecPlusK = function(v,k){
    return cc.p(v.x + k, v.y + k);
};
var VecMinusK = function(v,k){
    return cc.p(v.x - k, v.y - k);
};
var VecPlusVec = function(v,v2){
    return cc.p(v.x + v2.x, v.y + v2.y);
};
var VecMinusVec = function(v,v2){
    return cc.p(v.x - v2.x, v.y - v2.y);
};
var VecDotVec = function(v1,v2){
    return (v1.x*v2.x + v1.y+v2.y);
};
var NormeOfVec = function(v){
    return Math.sqrt( (v.x * v.x) + (v.y * v.y) );
};
var NormalizeVec = function(v){
    var n = NormeOfVec(v);
    return VecDivK(v,n);
};

var Vec1ProjectOnVec2 = function(v1,v2){
    var d = VecDotVec(v1,v2);
    var nV2 = NormeOfVec(v2);
    return VecMultK( NormalizeVec(v2), (d/nV2) );
};

function setAll(a, v) {
    var i, n = a.length;
    for (i = 0; i < n; ++i) {
        a[i] = v;
    }
};

function trunc(n){
   return n - n % 1;
}


var SizeX = 800;
var SizeY = 450;
var GRID_CASE_SIZE = 10;
var GRID_W = (SizeX / GRID_CASE_SIZE) +1;
var GRID_H = (SizeY / GRID_CASE_SIZE) +1;
var MAX_PARTICLES_IN_GRID_CASE = 49;
var SIZEOF_CASE_IN_GRID = 1+MAX_PARTICLES_IN_GRID_CASE;

// config 
var NUMBER_PARTICLES = 500;
var DRAW_METABALLS = false;
var DRAW_PHYSIC = true;
var PHYSIC_PRECISION = 2;
var PHYSIC_RADIUS = 2;
var METABALLS_RADIUS = 6;
var INIT_RANDOM_VELOCITY = false;

var USE_SAFE_THREAD = true;

var USE_BOX2D = false;
var USE_CUSTOM_PHYSIC = true;
var USE_REPULSION_FORCE = true;
var REPULSON_FORCE = 13.0;
var USE_SPACE_PARTITION = true;
var DRAW_SPACE_PARTITION_GRIG = true;

var g_Particles = [];
var g_UniformGrid = [];
var g_ParticlesSystem = new _ParticlesSystem();


var g_b2World = null;

function createParticle(){
    var p = new _particle();
    g_Particles.push(p);
    return (g_Particles.length - 1);
};

var ParticleType = {
    Dynamic : 0,
    Static : 1,
};

function _particle () {
    this._type = ParticleType.Dynamic;
    this._position = cc.p(0.0, 0.0);
    this._radius = 5.0;
    this._linearVelocity = cc.p(0.0, 0.0);
    this._force = cc.p(0.0, 0.0);
    this._impulse = cc.p(0.0, 0.0);
    this._mass = 1.0;
    this._invMass = 1.0;
	
	this._nextPos = cc.p(0.0, 0.0);
	this._nextVel = cc.p(0.0, 0.0);
	this._collidedInThreadSafeMode = false;
	
	this._xGrid = 0;
	this._yGrid = 0;
}


function _ParticlesSystem () {

    // Setp 0 : update
    this.update = function(dt){
        var coef = PHYSIC_PRECISION;
        var coefDt = dt / coef;
        for( var i=0; i<coef; i++){
			if( USE_SPACE_PARTITION ){
				this.constructGrid(dt);
			}
			this.computeCollisionReaction(coefDt);
            this.computeParticlesPoisition(coefDt);
        }
    };
    
    // /!\ not use yet /!\   Step 2: Grid Generation
    this.constructGrid = function(dt){
        // clear 
		if( g_UniformGrid.length < GRID_W*GRID_H*SIZEOF_CASE_IN_GRID ){
			g_UniformGrid.length = GRID_W*GRID_H*SIZEOF_CASE_IN_GRID;
		}
		setAll( g_UniformGrid, 0 );
		// set particles inside
		var Particles_number = g_Particles.length;
        for(var i=0; i < Particles_number; i++){
            this.findGridPosition( i, dt );
        }
    };
	this.findGridPosition = function(ParticleIdx,dt){
        var p = g_Particles[ ParticleIdx ];
		var X_Grid = trunc( p._position.x / GRID_CASE_SIZE );
		var Y_Grid = trunc( p._position.y / GRID_CASE_SIZE );
		
		var GRID_INDEX = ( (GRID_W * Y_Grid) + X_Grid ) * SIZEOF_CASE_IN_GRID;
		var nextFreePlace = g_UniformGrid[GRID_INDEX];
		
		if( nextFreePlace < MAX_PARTICLES_IN_GRID_CASE ){
			p._xGrid = X_Grid;
			p._yGrid = Y_Grid;
			g_UniformGrid[ GRID_INDEX + nextFreePlace + 1 ] = ParticleIdx;
			g_UniformGrid[GRID_INDEX] = g_UniformGrid[GRID_INDEX] + 1;
		} else {
			g_UniformGrid[GRID_INDEX] = g_UniformGrid[GRID_INDEX] + 1;
			console.log("No more free space in this case of the grid ! espace need :");
			console.log( g_UniformGrid[GRID_INDEX] );
		}
    };
	
	// Step 3: Collision Detection and Reaction
    this.computeCollisionReaction = function(dt){
        var Particles_number = g_Particles.length;
        for(var i=0; i < Particles_number; i++){
            if( USE_SPACE_PARTITION ){
				this.gridComputeCollisionReactionForPaticle( i, dt );
			} else {
				this.computeCollisionReactionForPaticle( i, dt );
			}
        }
    };
    this.computeCollisionReactionForPaticle = function(ParticleIdx,dt){
        var Particles_number = g_Particles.length;
        var p1 = g_Particles[ ParticleIdx ];
        if(p1._type == ParticleType.Static){return;}
        var p2 = null;
        for(var i=0; i < Particles_number; i++){
            if(i == ParticleIdx){continue;}

			this.elasticCollisionTestAndReactionBetween( ParticleIdx, i, dt  );
        }
    };
    this.gridComputeCollisionReactionForPaticle = function(ParticleIdx,dt){
        var Particles_number = g_Particles.length;
        var p1 = g_Particles[ ParticleIdx ];
        if(p1._type == ParticleType.Static){return;}
        var p2 = null;
        
		for(var Y_Grid = p1._yGrid - 1; Y_Grid<p1._yGrid + 1; Y_Grid++){
			if( Y_Grid < 0 || Y_Grid >= GRID_H){continue;}
			for(var X_Grid = p1._xGrid - 1; X_Grid<p1._xGrid + 1; X_Grid++){
				if( X_Grid < 0 || X_Grid >= GRID_W){continue;}
				var GRID_INDEX = ( (GRID_W * Y_Grid) + X_Grid ) * SIZEOF_CASE_IN_GRID;
				var number_particle_in_grid_case = g_UniformGrid[GRID_INDEX];
				for(var i=0; i < number_particle_in_grid_case; i++){
					if(ParticleIdx == g_UniformGrid[GRID_INDEX +1 +i]){continue;}
					
					this.elasticCollisionTestAndReactionBetween( ParticleIdx, g_UniformGrid[GRID_INDEX +1 +i], dt );
				}
			}
		}
    };
    this.elasticCollisionTestAndReactionBetween = function(ParticleIdx1,ParticleIdx2,dt){
		var p1 = g_Particles[ ParticleIdx1 ];
		var p2 = g_Particles[ ParticleIdx2 ];
					
		var RadiusSomme = p1._radius + p2._radius;
		var VectorDistance = VecMinusVec(p2._position,p1._position);
		var Distance = NormeOfVec(VectorDistance); 
	
		if( Distance < RadiusSomme ){
			var dr = (RadiusSomme - Distance)*2.0 + 0.0001;
			var m1 = p1._mass;
			var m2 = p2._mass;
			// compute normal vector and tangantiel vector of the collision
			var nx = (p2._position.x - p1._position.x)/(RadiusSomme);
			var ny = (p2._position.y - p1._position.y)/(RadiusSomme);
			var gx = -ny;
			var gy = nx;
			// compute actual velocity in this base
			var v1n = nx*p1._linearVelocity.x + ny*p1._linearVelocity.y;
			var v1g = gx*p1._linearVelocity.x + gy*p1._linearVelocity.y;
			var v2n = nx*p2._linearVelocity.x + ny*p2._linearVelocity.y;
			var v2g = gx*p2._linearVelocity.x + gy*p2._linearVelocity.y;
			// resolve velocity after collision in this base
			var nextV1 = (( v1n*(m1-m2) + 2*m2*v2n ) / (m1+m2));
			var nextV2 = (( v2n*(m2-m1) + 2*m1*v1n ) / (m1+m2));
			
			// update data of particle
			if(USE_SAFE_THREAD){
				p1._nextVel.x = nx*nextV1 +  gx*v1g;
				p1._nextVel.y = ny*nextV1 +  gy*v1g;
				if( USE_REPULSION_FORCE ){
					p1._nextVel.x -= nx * REPULSON_FORCE;
					p1._nextVel.y -= ny * REPULSON_FORCE;
				}
				
				p1._nextPos.x = p1._position.x - nx * dr;
				p1._nextPos.y = p1._position.y - ny * dr;
				p1._collidedInThreadSafeMode = true;
			} else {
				p1._linearVelocity.x = nx*nextV1 +  gx*v1g;
				p1._linearVelocity.y = ny*nextV1 +  gy*v1g;
				
				if(p2._type != ParticleType.Static) {
					p2._linearVelocity.x = nx*nextV2 +  gx*v2g;
					p2._linearVelocity.y = ny*nextV2 +  gy*v2g;
				}				
				p1._position.x = p1._position.x - nx * dr;
				p1._position.y = p1._position.y - ny * dr;
			}
		}
	};
    // Step 5: Computation of Position and Angle
    this.computeParticlesPoisition = function(dt){
		var Particles_number = g_Particles.length;
        for(var i=0; i < Particles_number; i++){
            this.computearticlePoisition(i,dt);
        }
    };
    this.computearticlePoisition = function(ParticleIdx,dt){
        var p = g_Particles[ ParticleIdx ];
        if(p._type == ParticleType.Static){ return; }
		
		if(USE_SAFE_THREAD){
			if( p._collidedInThreadSafeMode ){
				p._position = p._nextPos;
				p._linearVelocity = p._nextVel;
				p._collidedInThreadSafeMode = false;
			}
		}

        p._linearVelocity.x += dt * (p._force.x * p._invMass);
        p._linearVelocity.y += dt * (p._force.y * p._invMass);

        p._position.x += dt * p._linearVelocity.x;
        p._position.y += dt * p._linearVelocity.y;
    };
    
}





function __LimitToScreenSystem(){
    if(USE_CUSTOM_PHYSIC){
		var Particles_number = g_Particles.length;
		for(var i=0; i < Particles_number; i++){
			var p = g_Particles[ i ];
			p._position.x = p._position.x > SizeX ? 0 : p._position.x < 0 ? SizeX : p._position.x;
			p._position.y = p._position.y > SizeY ? 0 : p._position.y < 0 ? SizeY : p._position.y;
		}
	}
	if(USE_BOX2D){
		for (var b = g_b2World.GetBodyList(); b; b = b.GetNext()) {
			b.SetPosition( new b2Vec2( 
		/* X */ b.GetPosition().x > SizeX ? 0 : b.GetPosition().x < 0 ? SizeX : b.GetPosition().x,
		/* Y */ b.GetPosition().y > SizeY ? 0 : b.GetPosition().y < 0 ? SizeY : b.GetPosition().y ) 
			);
        }
	}
};

function __DrawAsCircleSystem(){ 
	if(USE_CUSTOM_PHYSIC){
		var Particles_number = g_Particles.length;
		for(var i=0; i < Particles_number; i++){
			var p = g_Particles[ i ];
			cc.drawingUtil.drawCircle(p._position, p._radius, 0, 10, false, true, '#008000' );
			cc.drawingUtil.drawCircle(p._position, p._radius, 0, 10, false, false, '#000000');
		}
	}
	if(USE_BOX2D){
		for (var b = g_b2World.GetBodyList(); b; b = b.GetNext()) {
			
			cc.drawingUtil.drawCircle(cc.p(b.GetPosition().x,b.GetPosition().y), PHYSIC_RADIUS, 0, 10, false, true, '#800000' );
			cc.drawingUtil.drawCircle(cc.p(b.GetPosition().x,b.GetPosition().y), PHYSIC_RADIUS, 0, 10, false, false, '#000000');
        }
	}
	if(USE_SPACE_PARTITION && DRAW_SPACE_PARTITION_GRIG){
		for(var i = 0; i<SizeY; i+=GRID_CASE_SIZE){
			cc.drawingUtil.drawLine( cc.p(0,i) , cc.p(SizeX,i), '#000080' );
		}
		
		for(var j = 0; j<SizeX; j+=GRID_CASE_SIZE){
			cc.drawingUtil.drawLine( cc.p(j,0) , cc.p(j,SizeY), '#000080' );	
		}
	}
};




var _ParticlesScene = cc.Layer.extend({
   
    
    init:function () {
        var selfPointer = this;
        //////////////////////////////
        // 1. super init first
        this._super();

        this.setTouchEnabled(true);
        this.setKeyboardEnabled(true);
        
        
        var size = {width:SizeX,height:SizeY};
        
        
        // background

        var background = cc.Sprite.create("res/back_waterScene.png");
        background.setPosition(cc.p(SizeX, SizeY));
        background.setVisible(true);
        background.setAnchorPoint(cc.p(0.5, 0.5));
        this.addChild(background, 0);
        
    
        //Menu (back button)
        var items = new Array();
        var p = cc.p( (0) + 20 ,
                      (0) + 30 );
        items[0] = createButton("Back");
        items[0].setPosition( p );
        items[0].setCallback(this,
            function () {
                myApp.GoToScene( new HelloWorldScene() );
            });
        var myMenu = cc.MenuCustom.create(items);
        this.addChild(myMenu);
        
        // update
        this.scheduleUpdate();
        
		
		
		
		
		
		
		
		// init Metaballs World
		var custom_meta_world;
		var b2_meta_world;
		if( DRAW_METABALLS && USE_CUSTOM_PHYSIC ){
			custom_meta_world = metaworld(size.width,size.height);
			custom_meta_world.drawActualTest = 5;
			custom_meta_world.setPosition(cc.p(SizeX/2,SizeY/2));
			this.addChild(custom_meta_world);
		}
		if( DRAW_METABALLS && USE_BOX2D ){
			b2_meta_world = metaworld(size.width,size.height);
			b2_meta_world.drawActualTest = 5;
			b2_meta_world.setPosition(cc.p(SizeX/2,SizeY/2));
			this.addChild(b2_meta_world);
		}
    
	
		// init b2 world
		if( USE_BOX2D ){
			g_b2World = new b2World(new b2Vec2(0, -98.1), false);
			g_b2World.SetContinuousPhysics(false);
		}
        
		
		
		
        // setup dynamic objects
        var NumberOfParticles = NUMBER_PARTICLES;
        for(var i=0; i<NumberOfParticles; i++){
            
			var __pos = cc.p( RAND(700,SizeX),RAND(0,SizeY) );
			var __vel = cc.p( RAND(-150,150),RAND(-150,150) );
			
			if(USE_CUSTOM_PHYSIC){
				var idx = createParticle();
				var p = g_Particles[idx];
				p._mass = 1;
				p._radius = PHYSIC_RADIUS;
				p._position = __pos;
				if(INIT_RANDOM_VELOCITY){
					p._linearVelocity = __vel;
				}
				p._force.y = -98.1;
				
				// setup metaballs of custom physic
				if(DRAW_METABALLS){
					var meta = metaball (0, 0, METABALLS_RADIUS, function(dt){
						this._x = this.temp._position.x;
						this._y = this.temp._position.y;
					});
					meta.temp = p;
					custom_meta_world.addMetaball( meta );
				}
			}
            if( USE_BOX2D ){
				var bodyDef = new b2BodyDef();
				bodyDef.type = b2Body.b2_dynamicBody;
				bodyDef.position.Set( __pos.x, __pos.y );
				var body = g_b2World.CreateBody(bodyDef);
				if(INIT_RANDOM_VELOCITY){
					body.SetLinearVelocity( new b2Vec2(__vel.x,__vel.y) );
				}

				var dynamicCircle = new b2CircleShape();
				dynamicCircle.SetRadius( PHYSIC_RADIUS );

				var fixtureDef = new b2FixtureDef();
				fixtureDef.shape = dynamicCircle;
				fixtureDef.density = 1/PHYSIC_RADIUS;
				fixtureDef.friction = 0.0;
				fixtureDef.restitution  = 1.0;
				body.CreateFixture(fixtureDef);
				
				// setup metaballs of box2d
				if(DRAW_METABALLS){
					var meta = metaball (0, 0, METABALLS_RADIUS, function(dt){
						this._x = this.temp.GetPosition().x;
						this._y = this.temp.GetPosition().y;
					});
					meta.temp = body;
					b2_meta_world.addMetaball( meta );
				}
			}
        }
        
        // setup static objects
        var NumberOfStaticParticles = 81;
        for(var i=0; i<NumberOfStaticParticles; i++){
			var r = 10;
			var __pos = cc.p( r*i, 0 );

			if(USE_CUSTOM_PHYSIC){
				var idx = createParticle();
				var p = g_Particles[idx];
				
				p._type = ParticleType.Static; 
				p._mass = 1000000000000000;
				p._radius = r;
				p._position = __pos;
				// setup metaballs of custom physic
				if(DRAW_METABALLS){
					var meta = metaball (0, 0, 12, function(dt){
						this._x = this.temp._position.x;
						this._y = this.temp._position.y;
					});
					meta.temp = p;
					custom_meta_world.addMetaball( meta );
				}
			}
			
			if( USE_BOX2D ){
				var bodyDef = new b2BodyDef();
				bodyDef.type = b2Body.b2_staticBody;
				bodyDef.position.Set( __pos.x, __pos.y );
				var body = g_b2World.CreateBody(bodyDef);

				var dynamicCircle = new b2CircleShape();
				dynamicCircle.SetRadius( r );

				var fixtureDef = new b2FixtureDef();
				fixtureDef.shape = dynamicCircle;
				fixtureDef.density = 10000000000000;
				fixtureDef.friction = 0.0;
				fixtureDef.restitution  = 1.0;
				body.CreateFixture(fixtureDef);
				
				// setup metaballs of box2d
				if(DRAW_METABALLS){
					var meta = metaball (0, 0, 12, function(dt){
						this._x = this.temp.GetPosition().x;
						this._y = this.temp.GetPosition().y;
					});
					meta.temp = body;
					b2_meta_world.addMetaball( meta );
				}
			}
        }
        

        return true;
    },

    
    update:function (dt) {
        if(USE_CUSTOM_PHYSIC){
			g_ParticlesSystem.update(dt);
		}
		if( USE_BOX2D ){
			var velocityIterations = 1;
			var positionIterations = 1;
			g_b2World.Step(dt, velocityIterations, positionIterations);
		}
        __LimitToScreenSystem();
    },
    
    postDraw:function(ctx){
        if(DRAW_PHYSIC){
            __DrawAsCircleSystem();
        }
    },
    
    // inputs 
    onTouchesBegan:function (pTouch, event) {
        if( pTouch[0] )
        {
            var localTouch = this.convertTouchToNodeSpace(pTouch[0]);
            var worldTouch = this.convertToWorldSpace(localTouch);
            var b2WorldPos = transformToB2World(worldTouch);
            this.b2MousePosition = b2WorldPos;
        }
    },
    onTouchesMoved:function(pTouch,pEvent){
        if( pTouch[0] )
        {
            var localTouch = this.convertTouchToNodeSpace(pTouch[0]);
            var worldTouch = this.convertToWorldSpace(localTouch);
            worldTouch = cc.Director.getInstance().convertToGL(worldTouch);
            var b2WorldPos = new b2Vec2( worldTouch.x / PTM_RATIO, worldTouch.y / PTM_RATIO );
            
            this.b2MousePosition = b2WorldPos;
        }
    },
    onTouchesEnded:function (pTouch,pEvent){
    },
    onKeyUp:function(e){
    },
    onKeyDown:function(e){
    },
});


var ParticlesScene = cc.Scene.extend({
    onEnter:function () {
        this._super();
        var layer = new _ParticlesScene();
        layer.init();
		layer.setPosition( cc.p(-SizeX/2.0,-SizeY/2.0) );
        this.addChild(layer);
    }
});


